# SingleTablePolymorphismBuilder.java

    package org.codefilarete.stalactite.engine.configurer.polymorphism;

    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;

    import org.codefilarete.reflection.PropertyAccessPoint;
    import org.codefilarete.reflection.PropertyMutator;
    import org.codefilarete.reflection.ReadWritePropertyAccessPoint;
    import org.codefilarete.reflection.ValueAccessPointMap;
    import org.codefilarete.reflection.ValueAccessPointSet;
    import org.codefilarete.stalactite.dsl.PolymorphismPolicy;
    import org.codefilarete.stalactite.dsl.PolymorphismPolicy.SingleTablePolymorphism;
    import org.codefilarete.stalactite.dsl.subentity.SubEntityMappingConfiguration;
    import org.codefilarete.stalactite.engine.configurer.AbstractIdentification;
    import org.codefilarete.stalactite.engine.configurer.NamingConfiguration;
    import org.codefilarete.stalactite.engine.configurer.builder.MainPersisterStep;
    import org.codefilarete.stalactite.engine.configurer.builder.PersisterBuilderContext;
    import org.codefilarete.stalactite.engine.configurer.builder.embeddable.EmbeddableMapping;
    import org.codefilarete.stalactite.engine.configurer.builder.embeddable.EmbeddableMappingBuilder;
    import org.codefilarete.stalactite.engine.runtime.AbstractPolymorphismPersister;
    import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
    import org.codefilarete.stalactite.engine.runtime.SimpleRelationalEntityPersister;
    import org.codefilarete.stalactite.engine.runtime.singletable.SingleTablePolymorphismPersister;
    import org.codefilarete.stalactite.mapping.DefaultEntityMapping;
    import org.codefilarete.stalactite.sql.ConnectionConfiguration;
    import org.codefilarete.stalactite.sql.Dialect;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.stalactite.sql.statement.binder.ColumnBinderRegistry;
    import org.codefilarete.tool.Reflections;
    import org.codefilarete.tool.exception.NotImplementedException;
    import org.codefilarete.tool.function.Converter;

    /**
     * @author Guillaume Mary
     */
    class SingleTablePolymorphismBuilder<C, I, T extends Table<T>, DTYPE> extends AbstractPolymorphicPersisterBuilder<C, I, T> {

    	private final Map<PropertyAccessPoint<C, ?>, Column<T, ?>> mainMapping;
    	private final Map<PropertyAccessPoint<C, ?>, Column<T, ?>> mainReadonlyMapping;
    	private final ValueAccessPointMap<C, Converter<Object, Object>, PropertyAccessPoint<C, ?>> mainReadConverters;
    	private final ValueAccessPointMap<C, Converter<Object, Object>, PropertyAccessPoint<C, ?>> mainWriteConverters;

    	SingleTablePolymorphismBuilder(SingleTablePolymorphism<C, DTYPE> polymorphismPolicy,
    								   AbstractIdentification<C, I> identification,
    								   ConfiguredRelationalPersister<C, I> mainPersister,
    								   Map<? extends PropertyAccessPoint<C, ?>, ? extends Column<T, ?>> mainMapping,
    								   Map<? extends PropertyAccessPoint<C, ?>, ? extends Column<T, ?>> mainReadonlyMapping,
    								   ValueAccessPointMap<C, ? extends Converter<Object, Object>, PropertyAccessPoint<C, ?>> mainReadConverters,
    								   ValueAccessPointMap<C, ? extends Converter<Object, Object>, PropertyAccessPoint<C, ?>> mainWriteConverters,
    								   ColumnBinderRegistry columnBinderRegistry,
    								   NamingConfiguration namingConfiguration,
    								   PersisterBuilderContext persisterBuilderContext) {
    		super(polymorphismPolicy, identification, mainPersister, columnBinderRegistry, namingConfiguration, persisterBuilderContext);
    		this.mainMapping = (Map<PropertyAccessPoint<C, ?>, Column<T, ?>>) mainMapping;
    		this.mainReadonlyMapping = (Map<PropertyAccessPoint<C, ?>, Column<T, ?>>) mainReadonlyMapping;
    		this.mainReadConverters = (ValueAccessPointMap<C, Converter<Object, Object>, PropertyAccessPoint<C, ?>>) mainReadConverters;
    		this.mainWriteConverters = (ValueAccessPointMap<C, Converter<Object, Object>, PropertyAccessPoint<C, ?>>) mainWriteConverters;
    	}

    	@Override
    	public AbstractPolymorphismPersister<C, I> build(Dialect dialect, ConnectionConfiguration connectionConfiguration) {
    		Map<Class<C>, ConfiguredRelationalPersister<C, I>> persisterPerSubclass = collectSubClassPersister(dialect, connectionConfiguration);

    		// Note that registering the cascades to sub-persisters must be done BEFORE the creation of the main persister to make it have all
    		// entities joins and let it build a consistent entity graph load; without it, we miss sub-relations when loading main entities
    		registerSubEntitiesRelations(persisterPerSubclass, dialect, connectionConfiguration);

    		Column<T, DTYPE> discriminatorColumn = ensureDiscriminatorColumn();
    		// NB: persisters are not registered into PersistenceContext because it may break implicit polymorphism principle (persisters are then
    		// available by PersistenceContext.getPersister(..)) and it is one sure that they are perfect ones (all their features should be tested)
    		SingleTablePolymorphismPersister<C, I, ?, DTYPE> result = new SingleTablePolymorphismPersister<>(
    			mainPersister, persisterPerSubclass, connectionConfiguration.getConnectionProvider(), dialect,
    			discriminatorColumn, (SingleTablePolymorphism<C, DTYPE>) polymorphismPolicy);

    		return result;
    	}

    	private <D extends C> Map<Class<D>, ConfiguredRelationalPersister<D, I>> collectSubClassPersister(Dialect dialect, ConnectionConfiguration connectionConfiguration) {
    		Map<Class<D>, ConfiguredRelationalPersister<D, I>> persisterPerSubclass = new HashMap<>();

    		T mainTable = (T) mainPersister.getMapping().getTargetTable();
    		for (SubEntityMappingConfiguration<D> subConfiguration : ((Set<SubEntityMappingConfiguration<D>>) (Set) polymorphismPolicy.getSubClasses())) {
    			persisterPerSubclass.put(subConfiguration.getEntityType(),
    									 buildSubclassPersister(dialect, connectionConfiguration, mainTable, subConfiguration));
    		}
    		return persisterPerSubclass;
    	}

    	private <D extends C> SimpleRelationalEntityPersister<D, I, T> buildSubclassPersister(Dialect dialect,
    																						  ConnectionConfiguration connectionConfiguration,
    																						  T mainTable,
    																						  SubEntityMappingConfiguration<D> subConfiguration) {
    		// as a difference with other polymorphic cases, we don't use the following line to get the target table, but
    		// only to ensure that configuration is right because it raises an exception if the user gave a column that is
    		// not part of target table
    		EmbeddableMappingBuilder.giveTargetTable(subConfiguration.getPropertiesMapping(), mainTable);

    		EmbeddableMappingBuilder<D, T> embeddableMappingBuilder = new EmbeddableMappingBuilder<>(subConfiguration.getPropertiesMapping(),
    				mainTable,
    				this.columnBinderRegistry,
    				this.namingConfiguration.getColumnNamingStrategy(),
    				this.namingConfiguration.getIndexNamingStrategy());
    		EmbeddableMapping<D, T> embeddableMapping = embeddableMappingBuilder.build();
    		// Primitive properties are mapped to not nullable columns, but single-table can't afford it because all columns are not set at all time
    		// so we need to set them to nullable, and we do it globally for simplicity (no primitive type check)
    		embeddableMapping.getMapping().values().forEach(column -> column.nullable(true));

    		Map<ReadWritePropertyAccessPoint<D, ?>, Column<T, ?>> subEntityPropertiesMapping = embeddableMapping.getMapping();
    		Map<PropertyMutator<D, ?>, Column<T, ?>> subEntityReadonlyPropertiesMapping = embeddableMapping.getReadonlyMapping();
    		ValueAccessPointMap<D, Converter<Object, Object>, PropertyAccessPoint<D, ?>> subEntityPropertiesReadConverters = embeddableMapping.getReadConverters();
    		ValueAccessPointMap<D, Converter<Object, Object>, PropertyAccessPoint<D, ?>> subEntityPropertiesWriteConverters = embeddableMapping.getWriteConverters();
    		// in single-table polymorphism, main properties must be given to sub-entities ones, because CRUD operations are dispatched to them
    		// by a proxy and main persister is not so much used
    		subEntityPropertiesMapping.putAll((Map) mainMapping);
    		subEntityReadonlyPropertiesMapping.putAll((Map) mainReadonlyMapping);
    		subEntityPropertiesReadConverters.putAll((Map) mainReadConverters);
    		subEntityPropertiesWriteConverters.putAll((Map) mainWriteConverters);
    		DefaultEntityMapping<D, I, T> entityMapping = MainPersisterStep.createEntityMapping(
    				true,    // given Identification (which is parent one) contains identifier policy
    				mainTable,
    				subEntityPropertiesMapping,
    				subEntityReadonlyPropertiesMapping,
    				subEntityPropertiesReadConverters,
    				subEntityPropertiesWriteConverters,
    				new ValueAccessPointSet<>(),    // TODO: implement properties set by constructor feature in single-table polymorphism
    				(AbstractIdentification<D, I>) identification,
    				subConfiguration.getPropertiesMapping().getBeanType(),
    				null);
    		// we need to copy also shadow columns, made in particular for one-to-one owned by source side because foreign key is maintained through it
    		entityMapping.addShadowColumns((DefaultEntityMapping) mainPersister.getMapping());

    		// no primary key to add nor foreign key since table is the same as main one (single table strategy)
    		return new SimpleRelationalEntityPersister<>(entityMapping, dialect, connectionConfiguration);
    	}

    	@Override
    	protected void assertSubPolymorphismIsSupported(PolymorphismPolicy<? extends C> subPolymorphismPolicy) {
    		// Everything else than joined-tables is not implemented
    		// - single-table with single-table needs analysis
    		// - single-table with table-per-class is not implemented
    		// Written as a negative condition to explicitly say what we support
    		if (!(subPolymorphismPolicy instanceof PolymorphismPolicy.JoinTablePolymorphism)) {
    			throw new NotImplementedException("Combining single-table polymorphism policy with " + Reflections.toString(subPolymorphismPolicy.getClass()));
    		}
    	}

    	private Column<T, DTYPE> ensureDiscriminatorColumn() {
    		Column<T, DTYPE> result = mainPersister.<T>getMapping().getTargetTable().addColumn(
    				((SingleTablePolymorphism<C, DTYPE>) polymorphismPolicy).getDiscriminatorColumn(),
    				((SingleTablePolymorphism<C, DTYPE>) polymorphismPolicy).getDiscrimintorType());
    		result.setNullable(false);
    		return result;
    	}

    }